<?php

/*
 * Copyright (C) 2009 - 2011 Pham Cong Dinh
 *
 * This file is part of Spica.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/

/**
 * This class provides method to display various debug information about the current request/response.
 *
 * namespace spica\core
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 22, 2009
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Debug.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaDebug
{
    /**
     * Backtrace format in HTML
     */
    const BACKTRACE_HTML = 1;

    /**
     * Backtrace format in plain text
     */
    const BACKTRACE_TEXT = 2;

    /**
     * Returns the calling context's calling filename.
     *
     * If the $shift parameter is specified, it will be used to skip the given number
     * of positions in the call stack, to find the correct place to look for the caller.
     * This is useful when the call stack is peppered with non-php code.
     *
     * @throws OutOfBoundsException
     * @param  int $shift The number of stack positions to skip to look for the caller.
     * @return string
     */
    public static function getCallerFilename($shift = 0)
    {
        /**
         * The caller of our caller is on
         * the second position of the stack
         */
        if (count($stack = debug_backtrace()) >= 2 + $shift)
        {
            return $stack[1 + $shift]['file'];
        }

        throw new OutOfBoundsException('Provided shift argument out of call stack bounds');
    }

    /**
     * Gets the calling line of the function it was called from,
     * i.e., this function's grandparent.
     *
     * @returns string Name of calling function
     */
    public static function getCallerLine($levels = 2)
    {
        $bt   = debug_backtrace();
        $file = isset($bt[$levels]['file']) ? $bt[$levels]['file'] : '';
        $line = isset($bt[$levels]['line']) ? $bt[$levels]['line'] : '';
        return array($file, $line);
    }

    /**
     * Gets caller information.
     *
     * @return array An array[function, class, line, type]
     */
    public static function getCaller($level = 1)
    {
        $bt = debug_backtrace();
        return (true === isset($bt[$level])) ? $bt[$level] : array();
    }

    /**
     * Retrieves debug trace as a string.
     *
     * @return string
     */
    public static function getTrace($format = self::BACKTRACE_TEXT)
    {
        if (self::BACKTRACE_HTML === $format)
        {
            $output  = date('Y-m-d h:i:s')."<br />";
            $output .= '<div style="text-align: left; font-family: monospace;">'."\n";
            $output .= '<b>Backtrace:</b><br />'."\n";
        }
        else
        {
            $output  = date('Y-m-d h:i:s')."\n";
            $output .= 'Backtrace: '."\n";
        }

        $bt = debug_backtrace();
        unset($bt[0]);

        foreach ($bt as $row => $bt)
        {
            $args  = '';

            if (true === isset($bt['args']))
            {
                foreach ($bt['args'] as $a)
                {
                    if (false  === empty($args))
                    {
                        $args .= ', ';
                    }

                    $args .= self::_getArgument($a);
                }
            }

            $file     = isset($bt['file']) ? $bt['file'] : '';
            $line     = isset($bt['line']) ? $bt['line'] : '';
            $class    = isset($bt['class']) ? $bt['class'] : '';
            $function = isset($bt['function']) ? $bt['function'] : '';
            $type     = isset($bt['type']) ? $bt['type'] : '';

            if ('' !== $function)
            {
                $args = $function."($args)";
            }

            if (self::BACKTRACE_HTML === $format)
            {
                $output .= '<br />'."\n";
                $output .= '<b>file:</b> '.$file.':'.$line.'<br />'."\n";
                $output .= '<b>call:</b> '.$class.$type.'<br />'."\n";
            }
            else
            {
                $output .= "\n";
                $output .= 'file: '.$file.':'.$line."\n";
                $output .= 'call: '.$class.$type.$args."\n";
            }
        }

        if (self::BACKTRACE_HTML === $format)
        {
            return $output.'</div>'."\n";
        }

        return $output."\n";
    }

    /**
     * Gets argument list as a string.
     *
     * @param mixed $a
     * @return string
     */
    private static function _getArgument($a)
    {
        $args = '';

        switch (gettype($a))
        {
            case 'integer':

            case 'double':
                $args .= $a;
                break;

            case 'string':
                $a     = htmlspecialchars(substr($a, 0, 100)).((strlen($a) > 100) ? '...' : '');
                $args .= '"'.$a.'"';
                break;

            case 'array':
               
                $args .= 'array(';
                $separtor = '';

                foreach ($a as $k => $v)
                {
                    $args .= $separtor.self::_getArgument($k).' => '.self::_getArgument($v);
                    $separtor = ', ';
                }
                $args .= ')';
                break;

            case 'object':
                $args .= 'object('.get_class($a).')';
                break;

            case 'resource':
                $args .= 'resource('.get_resource_type($a).')';
                break;

            case 'boolean':
                $args .= $a ? 'true' : 'false';
                break;

            case 'NULL':
                $args .= 'null';
                break;

            default:
                $args .= 'Unknown';
        }

        return $args;
    }

    /**
     * Transforms exception trace to a string.
     *
     * @param  array $trace Exception trace
     * @return string
     */
    public static function getTraceAsString($trace)
    {
        $s = '';

        foreach ($trace as $i => $item)
        {
            $s .= "#$i " . (isset($item['class']) ? $item['class'] . $item['type'] : '') . $item['function']
                . '(' . self::_getArgument($item['args']) . ") at [{$item['file']}:{$item['line']}]\n";
        }
        
        return $s;
    }
}

?>